package com.itheima.controller;

import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.itheima.utils.AliSendUtils;
import com.itheima.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.dto.UserDTO;
import com.itheima.entity.ExcelListener;
import com.itheima.entity.ExcelModel;
import com.itheima.entity.OrderPark;
import com.itheima.entity.OrderParkDTO;
import com.itheima.enums.MsgCode;
import com.itheima.service.OrderService;
import com.itheima.service.UserService;
import com.itheima.utils.ResultUtils;
import com.itheima.vo.PageCommonVO;

import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {

    @Autowired
    private OrderService orderService;
    @Autowired
    private UserService userService;
    @Autowired
    private RedisUtils redisUtils;
//    @Autowired
//    private AliSendUtils aliSendUtils;

    /** 短信模板 */
    @Value("${TemplateCode}")
    private String templateCode;

    // 商品售完内存标记值 多线程不能用hashMap
    public static ConcurrentHashMap<String, Boolean> sellOutMap = new ConcurrentHashMap<>();


    // 模拟当前登录用户
    public UserDTO getLoginUser(){
        UserDTO userDTO = new UserDTO();
        userDTO.setUserPhone("18668729211");
        userDTO.setIdCard("1644846");
        return userDTO;
    }

    /**
     * 初始化秒杀商品库存放入redis缓存
     */
//    @PostConstruct
//    public void init(){
//        String type = "skill"; // 秒杀商品
//        List<OrderPark> list = orderService.querySkillGoods(type);
//        for (OrderPark orderPark : list) {
//            redisTemplate.opsForValue().set("productId",orderPark.getParkPrice());
//        }
//    }

    @GetMapping("send")
    public void send(String phone, String code) {
        AliSendUtils.send(phone, code);
    }

    /**
     * 秒杀抢购
     * @param token 秒杀接口访问令牌, 防重刷
     * @param productId 商品ID
     */
    @PostMapping("{token}/skillGoods")
    public String skillGoods(String productId, @PathVariable String token){
        /** 参数校验 */
        if (StringUtils.isEmpty(productId) || StringUtils.isEmpty(token)){
            return "商品ID或访问地址参数token为空";
        }
        UserDTO loginUser = getLoginUser();
        if (Objects.isNull(loginUser)){
            return "用户必须登录才能秒杀";
        }
        /** token校验 */
        boolean check = checkToken(loginUser.getUserId(),productId,token);
        if (!check){
            return "非法请求!"; // token不一致
        }
        // 用jvm内存里的商品库存校验可以大大提高性能, 比Redis库存判断减少了于Redis交互次数
        if (sellOutMap.get(productId) != null){
            return "商品已抢完";
        }

        String skillKey = loginUser.getUserId() + "_" + productId;
        // 查询生成订单的Redis缓存,一个用户限购一个商品
        OrderPark order = redisUtils.get(skillKey, OrderPark.class);
        if (order != null){
            return  "您已参与过秒杀活动, 不能重复参加";
        }
        // 设置排队标志, 分布式锁控只能一个线程获取竞争锁, 失败则没获取资格,
        // 之前下过一次单的用户可能还在处理中,针对用户疯狂点击, 用户第一次会跳过
        // 该分布式锁不能锁住大量不同用户进来
        if (!redisUtils.setnx(skillKey,"lock", 60, TimeUnit.SECONDS)) {
            return "排队中, 请稍后..."; // 分布式锁阻塞作用
        }

        // 先从redis直接扣减库存, 事先已把秒杀的商品库存放入到redis缓存
        Long stock = redisUtils.decrement(productId);
        if (stock < 0){
            /**
             * 防止前10个用户抢到商品, 后面10000个没有抢到任会把redis减到负数
             * 万一下单失败或者用户退单,虽然会执行incr命令恢复,但是 (-10000 + 1)还是负数
             * 会造成还有一个订单库存不会被抢到, 所以只要stock小于0就恢复一下(设置为0也一样)
             */
            redisUtils.increment(productId);
            sellOutMap.put(productId,true);// 商品已售完标记
            return "商品已经售完!";
        }


        /**  TODO 判断商品的秒杀截止时间和当前时间的大小 */
        long now = System.currentTimeMillis();
//        Product product = DB.selectByPrimaryKey(productId);
//        if (now < product.getStartTime()){
//            return "秒杀活动还未开始";
//        } if (now > product.getEndTime()){
//            return "秒杀活动已结束";
//        }
        try { //  调订单Service接口生成订单 (一般用MQ发送异步消息处理...)
            OrderPark orderPark = orderService.createOrder(productId, loginUser.getUserId());
        } catch (Exception e) {
            // 创建订单异常,把之前缓存扣减的库存恢复
            redisUtils.increment(productId);
            // 订单异常, 去掉库存售完标记
            sellOutMap.remove(productId);
        } finally {
            // 清空下单排队标志
            redisUtils.delete(skillKey);
        }
        return "0"; // 0:前端显示排队处理中...
    }

    /**
     * 点击秒杀后, 前端轮训查询订单结果
     */
    @PostMapping("getResult")
    public String getResult(String productId, Integer userId){
        UserDTO loginUser = getLoginUser();
        if (Objects.isNull(loginUser)){
            return "用户必须登录才能秒杀";
        }
        String skillKey = loginUser.getUserId() + "_" + productId;
        // 判断redis的排队标记, 不为空返回还在排队中(注意: 一定要先判断排队标记再判断是否生成订单)
        // 因为异步下单可能还在处理中(清空下单排队标志没有删除),一旦删除,说明订单处理完成(正常异常都会删除)
        if (redisUtils.get(skillKey,String.class) != null){
            return "0"; // 0:前端显示正在排队处理中...
        }

        // 查询用户是否创建订单成功, 到这一步一定是订单处理后删除了排队标记(正常异常都会删除)
        OrderPark orderPark = redisUtils.get(skillKey, OrderPark.class);
        if (orderPark != null){
            return "1"; // 正常完成订单,秒杀成功
        }else {
            return "-1"; // 创建订单异常了, 秒杀失败
        }


    }


    /**
     * 点击立即秒杀时先请求获取一个token
     * 前端获取到token后才真正调秒杀接口
     * @param productId 商品ID
     */
    @GetMapping("getToken")
    public JSONObject getToken(String productId, HttpServletRequest request){
        // 用户登录才允许操作
        UserDTO loginUser = getLoginUser();
        if (Objects.isNull(loginUser)){
           return ResultUtils.failureMsg("必须登录才能参与秒杀");
        }

        // 利用Redis做接口限流, 每分钟只允许10000个用户请求
        Long requestCount = redisUtils.increment(request.getRequestURI());
        if (requestCount == 1){ // 第一个请求
            redisUtils.expire(request.getRequestURI(),60,TimeUnit.SECONDS);
        }
        else if (requestCount > 100000){
           return ResultUtils.failureMsg("访问太频繁, 请稍后再试");
        }

        // 生成token, 存入Redis
        String token = UUID.randomUUID().toString();
        String tokenKey = "skill_token_" + loginUser.getUserId() + "_" + productId;
        redisUtils.set(tokenKey, token, 60, TimeUnit.SECONDS);
        return ResultUtils.successData(token,"SUCCESS");
    }

    /**
    * 根据不同级别的VIP计算实际价格
    * @Author: yuan_he_song
    * @Date: 2019-10-23 15:04
    */
    @PostMapping("countFee")
    public JSONObject countFee(String orderPrice,Integer vipType){
        BigDecimal price = new BigDecimal(orderPrice);
        BigDecimal payPrice = orderService.payPrice(price, vipType);
        return ResultUtils.successData(payPrice,MsgCode.SUCCESS.getMsg());
    }

    /**
    * @Description:  查所有的未完成订单列表
    * @Param:  parkid 车场id
    * @Author: yuan_he_song
    * @Date: 2019-08-14 16:25
    */
    @PostMapping("/list")
    public JSONObject findList(PageCommonVO pageCommonVO)throws Exception{
        // 参数校验
        if(pageCommonVO.checkParams()){
            return ResultUtils.failureData(MsgCode.PARAM_MISSING);
        }
        PageHelper.startPage(pageCommonVO.getPageNum(),pageCommonVO.getPageSize());
        List<OrderParkDTO> list = new ArrayList<>();

        try {
          list = orderService.findAll(pageCommonVO.getParkid());
        }catch (Exception e){
            e.printStackTrace();
            return ResultUtils.failureData(MsgCode.EXCEPTION);
        }
        PageInfo<OrderParkDTO> pageInfo = new PageInfo<>(list);
        return ResultUtils.successAndPageData(list,pageInfo, MsgCode.SUCCESS.getMsg());
    }

    /**
    * @Description:  导出Excel
    * @Param:  parkid 车场id
    * @Author: yuan_he_song
    * @Date: 2019-08-14 16:26
    */
    @RequestMapping(value = "exportExcel",method = RequestMethod.GET)
    public void exportExcel(@RequestParam("parkid") Integer parkid, HttpServletResponse response){
        try {
            orderService.exportExcel(parkid , response);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
    * @Description:下载 Excel模板
    * @Author: yuan_he_song
    * @Date: 2019-10-23 11:48
    */
    @RequestMapping(value = "downloadTemplate",method = RequestMethod.GET)
    public void downloadTemplate(HttpServletResponse response){
        try {
            orderService.downloadTemplate(response);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * @Description: Excel导入
     * 导入前先下载好模板
     */
    @PostMapping(value = "importExcel")
    public JSONObject importExcel(@RequestParam("file") MultipartFile file){
        InputStream inputStream = null;
        try {
            inputStream  = file.getInputStream();
            /** 解析每行结果在listener中处理 */
            AnalysisEventListener listener = new ExcelListener(userService);
            ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLSX, null, listener);
            excelReader.read(new Sheet(1, 1, ExcelModel.class));// TODO ExcelModel模型类 必须有无惨构造!!!
        } catch (Exception e) {
            log.error("批量导入失败",e);
        }
        return ResultUtils.successData(null,"导入成功!");
    }

    /**
     *  验证令牌的有效性
     *  token 做令牌都是一次性的,用完删除
     */
    private boolean checkToken(Integer userId, String productId, String token) {
        // 从Redis中取出token令牌
        String tokenKey = "skill_token_" + userId + "_" + productId ;
        String realToken = redisUtils.get(tokenKey, String.class);
        boolean result = token.equals(realToken);
        redisUtils.delete(tokenKey);
        return result;
    }


}
